 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.resources;

 import java.io.*;
 import java.util.*;
 import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
 import org.eclipse.core.internal.localstore.SafeFileInputStream;
 import org.eclipse.core.internal.utils.Messages;
 import org.eclipse.core.internal.utils.Policy;
 import org.eclipse.core.internal.watson.*;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.osgi.util.NLS;

 /**
  * A marker manager stores and retrieves markers on resources in the workspace.
  */
 public class MarkerManager implements IManager {

     //singletons
 private static final MarkerInfo[] NO_MARKER_INFO = new MarkerInfo[0];
     private static final IMarker[] NO_MARKERS = new IMarker[0];
     protected MarkerTypeDefinitionCache cache = new MarkerTypeDefinitionCache();
     private long changeId = 0;
     protected Map currentDeltas = null;
     protected final MarkerDeltaManager deltaManager = new MarkerDeltaManager();

     protected Workspace workspace;
     protected MarkerWriter writer = new MarkerWriter(this);

     /**
      * Creates a new marker manager
      */
     public MarkerManager(Workspace workspace) {
         this.workspace = workspace;
     }

     /* (non-Javadoc)
      * Adds the given markers to the given resource.
      *
      * @see IResource#createMarker(String)
      */
     public void add(IResource resource, MarkerInfo newMarker) throws CoreException {
         Resource target = (Resource) resource;
         ResourceInfo info = workspace.getResourceInfo(target.getFullPath(), false, false);
         target.checkExists(target.getFlags(info), false);
         info = workspace.getResourceInfo(resource.getFullPath(), false, true);
         //resource may have been deleted concurrently -- just bail out if this happens
 if (info == null)
             return;
         // set the M_MARKERS_SNAP_DIRTY flag to indicate that this
 // resource's markers have changed since the last snapshot
 if (isPersistent(newMarker))
             info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
         //Concurrency: copy the marker set on modify
 MarkerSet markers = info.getMarkers(true);
         if (markers == null)
             markers = new MarkerSet(1);
         basicAdd(resource, markers, newMarker);
         if (!markers.isEmpty())
             info.setMarkers(markers);
     }

     /**
      * Adds the new markers to the given set of markers. If added, the markers
      * are associated with the specified resource.IMarkerDeltas for Added markers
      * are generated.
      */
     private void basicAdd(IResource resource, MarkerSet markers, MarkerInfo newMarker) throws CoreException {
         // should always be a new marker.
 if (newMarker.getId() != MarkerInfo.UNDEFINED_ID) {
             String message = Messages.resources_changeInAdd;
             throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, resource.getFullPath(), message));
         }
         newMarker.setId(workspace.nextMarkerId());
         markers.add(newMarker);
         IMarkerSetElement[] changes = new IMarkerSetElement[1];
         changes[0] = new MarkerDelta(IResourceDelta.ADDED, resource, newMarker);
         changedMarkers(resource, changes);
     }

     /**
      * Returns the markers in the given set of markers which match the given type.
      */
     protected MarkerInfo[] basicFindMatching(MarkerSet markers, String type, boolean includeSubtypes) {
         int size = markers.size();
         if (size <= 0)
             return NO_MARKER_INFO;
         List result = new ArrayList(size);
         IMarkerSetElement[] elements = markers.elements();
         for (int i = 0; i < elements.length; i++) {
             MarkerInfo marker = (MarkerInfo) elements[i];
             // if the type is null then we are looking for all types of markers
 if (type == null)
                 result.add(marker);
             else {
                 if (includeSubtypes) {
                     if (cache.isSubtype(marker.getType(), type))
                         result.add(marker);
                 } else {
                     if (marker.getType().equals(type))
                         result.add(marker);
                 }
             }
         }
         size = result.size();
         if (size <= 0)
             return NO_MARKER_INFO;
         return (MarkerInfo[]) result.toArray(new MarkerInfo[size]);
     }

     protected int basicFindMaxSeverity(MarkerSet markers, String type, boolean includeSubtypes) {
         int max = -1;
         int size = markers.size();
         if (size <= 0)
             return max;
         IMarkerSetElement[] elements = markers.elements();
         for (int i = 0; i < elements.length; i++) {
             MarkerInfo marker = (MarkerInfo) elements[i];
             // if the type is null then we are looking for all types of markers
 if (type == null)
                 max = Math.max(max, getSeverity(marker));
             else {
                 if (includeSubtypes) {
                     if (cache.isSubtype(marker.getType(), type))
                         max = Math.max(max, getSeverity(marker));
                 } else {
                     if (marker.getType().equals(type))
                         max = Math.max(max, getSeverity(marker));
                 }
             }
             if (max >= IMarker.SEVERITY_ERROR) {
                 break;
             }
         }
         return max;
     }

     private int getSeverity(MarkerInfo marker) {
         Object o = marker.getAttribute(IMarker.SEVERITY);
         if (o instanceof Integer ) {
             Integer i = (Integer ) o;
             return i.intValue();
         }
         return -1;
     }

     /**
      * Removes markers of the specified type from the given resource.
      * Note: this method is protected to avoid creation of a synthetic accessor (it
      * is called from an anonymous inner class).
      */
     protected void basicRemoveMarkers(ResourceInfo info, IPathRequestor requestor, String type, boolean includeSubtypes) {
         MarkerSet markers = info.getMarkers(false);
         if (markers == null)
             return;
         IMarkerSetElement[] matching;
         IPath path;
         if (type == null) {
             // if the type is null, all markers are to be removed.
 //now we need to crack open the tree
 path = requestor.requestPath();
             info = workspace.getResourceInfo(path, false, true);
             info.setMarkers(null);
             matching = markers.elements();
         } else {
             matching = basicFindMatching(markers, type, includeSubtypes);
             // if none match, there is nothing to remove
 if (matching.length == 0)
                 return;
             //now we need to crack open the tree
 path = requestor.requestPath();
             info = workspace.getResourceInfo(path, false, true);
             //Concurrency: copy the marker set on modify
 markers = info.getMarkers(true);
             // remove all the matching markers and also the whole
 // set if there are no remaining markers
 markers.removeAll(matching);
             info.setMarkers(markers.size() == 0 ? null : markers);
         }
         info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
         IMarkerSetElement[] changes = new IMarkerSetElement[matching.length];
         IResource resource = workspace.getRoot().findMember(path);
         for (int i = 0; i < matching.length; i++)
             changes[i] = new MarkerDelta(IResourceDelta.REMOVED, resource, (MarkerInfo) matching[i]);
         changedMarkers(resource, changes);
         return;
     }

     /**
      * Adds the markers on the given target which match the specified type to the list.
      */
     protected void buildMarkers(IMarkerSetElement[] markers, IPath path, int type, ArrayList list) {
         if (markers.length == 0)
             return;
         IResource resource = workspace.newResource(path, type);
         list.ensureCapacity(list.size() + markers.length);
         for (int i = 0; i < markers.length; i++) {
             list.add(new Marker(resource, ((MarkerInfo) markers[i]).getId()));
         }
     }

     /**
      * Markers have changed on the given resource. Remember the changes for subsequent notification.
      */
     protected void changedMarkers(IResource resource, IMarkerSetElement[] changes) {
         if (changes == null || changes.length == 0)
             return;
         changeId++;
         if (currentDeltas == null)
             currentDeltas = deltaManager.newGeneration(changeId);
         IPath path = resource.getFullPath();
         MarkerSet previousChanges = (MarkerSet) currentDeltas.get(path);
         MarkerSet result = MarkerDelta.merge(previousChanges, changes);
         if (result.size() == 0)
             currentDeltas.remove(path);
         else
             currentDeltas.put(path, result);
         ResourceInfo info = workspace.getResourceInfo(path, false, true);
         if (info != null)
             info.incrementMarkerGenerationCount();
     }

     /**
      * Returns the marker with the given id or <code>null</code> if none is found.
      */
     public IMarker findMarker(IResource resource, long id) {
         MarkerInfo info = findMarkerInfo(resource, id);
         return info == null ? null : new Marker(resource, info.getId());
     }

     /**
      * Returns the marker with the given id or <code>null</code> if none is found.
      */
     public MarkerInfo findMarkerInfo(IResource resource, long id) {
         ResourceInfo info = workspace.getResourceInfo(resource.getFullPath(), false, false);
         if (info == null)
             return null;
         MarkerSet markers = info.getMarkers(false);
         if (markers == null)
             return null;
         return (MarkerInfo) markers.get(id);
     }

     /**
      * Returns all markers of the specified type on the given target, with option
      * to search the target's children.
      * Passing <code>null</code> for the type specifies a match
      * for all types (i.e., <code>null</code> is a wildcard.
      */
     public IMarker[] findMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) {
         ArrayList result = new ArrayList();
         doFindMarkers(target, result, type, includeSubtypes, depth);
         if (result.size() == 0)
             return NO_MARKERS;
         return (IMarker[]) result.toArray(new IMarker[result.size()]);
     }

     /**
      * Fills the provided list with all markers of the specified type on the given target,
      * with option to search the target's children.
      * Passing <code>null</code> for the type specifies a match
      * for all types (i.e., <code>null</code> is a wildcard.
      */
     public void doFindMarkers(IResource target, ArrayList result, final String type, final boolean includeSubtypes, int depth) {
         //optimize the deep searches with an element tree visitor
 if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
             visitorFindMarkers(target.getFullPath(), result, type, includeSubtypes);
         else
             recursiveFindMarkers(target.getFullPath(), result, type, includeSubtypes, depth);
     }

     /**
      * Finds the max severity across all problem markers on the given target,
      * with option to search the target's children.
      */
     public int findMaxProblemSeverity(IResource target, String type, boolean includeSubtypes, int depth) {
         //optimize the deep searches with an element tree visitor
 if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
             return visitorFindMaxSeverity(target.getFullPath(), type, includeSubtypes);
         return recursiveFindMaxSeverity(target.getFullPath(), type, includeSubtypes, depth);
     }

     public long getChangeId() {
         return changeId;
     }

     /**
      * Returns the map of all marker deltas since the given change Id.
      */
     public Map getMarkerDeltas(long startChangeId) {
         return deltaManager.assembleDeltas(startChangeId);
     }

     /**
      * Returns true if this manager has a marker delta record
      * for the given marker id, and false otherwise.
      */
     boolean hasDelta(IPath path, long id) {
         if (currentDeltas == null)
             return false;
         MarkerSet set = (MarkerSet) currentDeltas.get(path);
         if (set == null)
             return false;
         return set.get(id) != null;
     }

     /**
      * Returns true if the given marker is persistent, and false
      * otherwise.
      */
     public boolean isPersistent(MarkerInfo info) {
         if (!cache.isPersistent(info.getType()))
             return false;
         Object isTransient = info.getAttribute(IMarker.TRANSIENT);
         return isTransient == null || !(isTransient instanceof Boolean ) || !((Boolean ) isTransient).booleanValue();
     }

     /**
      * Returns true if <code>type</code> is a sub type of <code>superType</code>.
      */
     public boolean isSubtype(String type, String superType) {
         return cache.isSubtype(type, superType);
     }

     public void moved(final IResource source, final IResource destination, int depth) throws CoreException {
         final int count = destination.getFullPath().segmentCount();

         // we removed from the source and added to the destination
 IResourceVisitor visitor = new IResourceVisitor() {
             public boolean visit(IResource resource) {
                 Resource r = (Resource) resource;
                 ResourceInfo info = r.getResourceInfo(false, true);
                 MarkerSet markers = info.getMarkers(false);
                 if (markers == null)
                     return true;
                 info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
                 IMarkerSetElement[] removed = new IMarkerSetElement[markers.size()];
                 IMarkerSetElement[] added = new IMarkerSetElement[markers.size()];
                 IPath path = resource.getFullPath().removeFirstSegments(count);
                 path = source.getFullPath().append(path);
                 IResource sourceChild = workspace.newResource(path, resource.getType());
                 IMarkerSetElement[] elements = markers.elements();
                 for (int i = 0; i < elements.length; i++) {
                     // calculate the ADDED delta
 MarkerInfo markerInfo = (MarkerInfo) elements[i];
                     MarkerDelta delta = new MarkerDelta(IResourceDelta.ADDED, resource, markerInfo);
                     added[i] = delta;
                     // calculate the REMOVED delta
 delta = new MarkerDelta(IResourceDelta.REMOVED, sourceChild, markerInfo);
                     removed[i] = delta;
                 }
                 changedMarkers(resource, added);
                 changedMarkers(sourceChild, removed);
                 return true;
             }
         };
         destination.accept(visitor, depth, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
     }

     /**
      * Adds the markers for a subtree of resources to the list.
      */
     private void recursiveFindMarkers(IPath path, ArrayList list, String type, boolean includeSubtypes, int depth) {
         ResourceInfo info = workspace.getResourceInfo(path, false, false);
         if (info == null)
             return;
         MarkerSet markers = info.getMarkers(false);

         //add the matching markers for this resource
 if (markers != null) {
             IMarkerSetElement[] matching;
             if (type == null)
                 matching = markers.elements();
             else
                 matching = basicFindMatching(markers, type, includeSubtypes);
             buildMarkers(matching, path, info.getType(), list);
         }

         //recurse
 if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
             return;
         if (depth == IResource.DEPTH_ONE)
             depth = IResource.DEPTH_ZERO;
         IPath[] children = workspace.getElementTree().getChildren(path);
         for (int i = 0; i < children.length; i++) {
             recursiveFindMarkers(children[i], list, type, includeSubtypes, depth);
         }
     }

     /**
      * Finds the max severity across problem markers for a subtree of resources.
      */
     private int recursiveFindMaxSeverity(IPath path, String type, boolean includeSubtypes, int depth) {
         ResourceInfo info = workspace.getResourceInfo(path, false, false);
         if (info == null)
             return -1;
         MarkerSet markers = info.getMarkers(false);

         //add the matching markers for this resource
 int max = -1;
         if (markers != null) {
             max = basicFindMaxSeverity(markers, type, includeSubtypes);
             if (max >= IMarker.SEVERITY_ERROR) {
                 return max;
             }
         }

         //recurse
 if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
             return max;
         if (depth == IResource.DEPTH_ONE)
             depth = IResource.DEPTH_ZERO;
         IPath[] children = workspace.getElementTree().getChildren(path);
         for (int i = 0; i < children.length; i++) {
             max = Math.max(max, recursiveFindMaxSeverity(children[i], type, includeSubtypes, depth));
             if (max >= IMarker.SEVERITY_ERROR) {
                 break;
             }
         }
         return max;
     }

     /**
      * Adds the markers for a subtree of resources to the list.
      */
     private void recursiveRemoveMarkers(final IPath path, String type, boolean includeSubtypes, int depth) {
         ResourceInfo info = workspace.getResourceInfo(path, false, false);
         if (info == null)//phantoms don't have markers
 return;
         IPathRequestor requestor = new IPathRequestor() {
             public String requestName() {
                 return path.lastSegment();
             }

             public IPath requestPath() {
                 return path;
             }
         };
         basicRemoveMarkers(info, requestor, type, includeSubtypes);
         //recurse
 if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE)
             return;
         if (depth == IResource.DEPTH_ONE)
             depth = IResource.DEPTH_ZERO;
         IPath[] children = workspace.getElementTree().getChildren(path);
         for (int i = 0; i < children.length; i++) {
             recursiveRemoveMarkers(children[i], type, includeSubtypes, depth);
         }
     }

     /**
      * Removes the specified marker
      */
     public void removeMarker(IResource resource, long id) {
         MarkerInfo markerInfo = findMarkerInfo(resource, id);
         if (markerInfo == null)
             return;
         ResourceInfo info = ((Workspace) resource.getWorkspace()).getResourceInfo(resource.getFullPath(), false, true);
         //Concurrency: copy the marker set on modify
 MarkerSet markers = info.getMarkers(true);
         int size = markers.size();
         markers.remove(markerInfo);
         // if that was the last marker remove the set to save space.
 info.setMarkers(markers.size() == 0 ? null : markers);
         // if we actually did remove a marker, post a delta for the change.
 if (markers.size() != size) {
             if (isPersistent(markerInfo))
                 info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
             IMarkerSetElement[] change = new IMarkerSetElement[] {new MarkerDelta(IResourceDelta.REMOVED, resource, markerInfo)};
             changedMarkers(resource, change);
         }
     }

     /**
      * Remove all markers for the given resource to the specified depth.
      */
     public void removeMarkers(IResource resource, int depth) {
         removeMarkers(resource, null, false, depth);
     }

     /**
      * Remove all markers with the given type from the node at the given path.
      * Passing <code>null</code> for the type specifies a match
      * for all types (i.e., <code>null</code> is a wildcard.
      */
     public void removeMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) {
         if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE)
             visitorRemoveMarkers(target.getFullPath(), type, includeSubtypes);
         else
             recursiveRemoveMarkers(target.getFullPath(), type, includeSubtypes, depth);
     }

     /**
      * Reset the marker deltas up to but not including the given start Id.
      */
     public void resetMarkerDeltas(long startId) {
         currentDeltas = null;
         deltaManager.resetDeltas(startId);
     }

     public void restore(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException {
         // first try and load the last saved file, then apply the snapshots
 restoreFromSave(resource, generateDeltas);
         restoreFromSnap(resource);
     }

     protected void restoreFromSave(IResource resource, boolean generateDeltas) throws CoreException {
         IPath sourceLocation = workspace.getMetaArea().getMarkersLocationFor(resource);
         IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(sourceLocation);
         java.io.File sourceFile = new java.io.File (sourceLocation.toOSString());
         java.io.File tempFile = new java.io.File (tempLocation.toOSString());
         if (!sourceFile.exists() && !tempFile.exists())
             return;
         try {
             DataInputStream input = new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString()));
             try {
                 MarkerReader reader = new MarkerReader(workspace);
                 reader.read(input, generateDeltas);
             } finally {
                 input.close();
             }
         } catch (Exception e) {
             //don't let runtime exceptions such as ArrayIndexOutOfBounds prevent startup
 String msg = NLS.bind(Messages.resources_readMeta, sourceLocation);
             throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e);
         }
     }

     protected void restoreFromSnap(IResource resource) {
         IPath sourceLocation = workspace.getMetaArea().getMarkersSnapshotLocationFor(resource);
         if (!sourceLocation.toFile().exists())
             return;
         try {
             DataInputStream input = new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile()));
             try {
                 MarkerSnapshotReader reader = new MarkerSnapshotReader(workspace);
                 while (true)
                     reader.read(input);
             } catch (EOFException eof) {
                 // ignore end of file
 } finally {
                 input.close();
             }
         } catch (Exception e) {
             // only log the exception, we should not fail restoring the snapshot
 String msg = NLS.bind(Messages.resources_readMeta, sourceLocation);
             Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e));
         }
     }

     public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List list) throws IOException {
         writer.save(info, requestor, output, list);
     }

     /* (non-Javadoc)
      * @see IManager#shutdown(IProgressMonitor)
      */
     public void shutdown(IProgressMonitor monitor) {
         // do nothing
 }

     public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException {
         writer.snap(info, requestor, output);
     }

     /* (non-Javadoc)
      * @see IManager#startup(IProgressMonitor)
      */
     public void startup(IProgressMonitor monitor) {
         // do nothing
 }

     /**
      * Adds the markers for a subtree of resources to the list.
      */
     private void visitorFindMarkers(IPath path, final ArrayList list, final String type, final boolean includeSubtypes) {
         IElementContentVisitor visitor = new IElementContentVisitor() {
             public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
                 ResourceInfo info = (ResourceInfo) elementContents;
                 if (info == null)
                     return false;
                 MarkerSet markers = info.getMarkers(false);

                 //add the matching markers for this resource
 if (markers != null) {
                     IMarkerSetElement[] matching;
                     if (type == null)
                         matching = markers.elements();
                     else
                         matching = basicFindMatching(markers, type, includeSubtypes);
                     buildMarkers(matching, requestor.requestPath(), info.getType(), list);
                 }
                 return true;
             }
         };
         new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
     }

     /**
      * Finds the max severity across problem markers for a subtree of resources.
      */
     private int visitorFindMaxSeverity(IPath path, final String type, final boolean includeSubtypes) {
         class MaxSeverityVisitor implements IElementContentVisitor {
             int max = -1;

             public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
                 // bail if an earlier sibling already hit the max
 if (max >= IMarker.SEVERITY_ERROR) {
                     return false;
                 }
                 ResourceInfo info = (ResourceInfo) elementContents;
                 if (info == null)
                     return false;
                 MarkerSet markers = info.getMarkers(false);

                 //add the matching markers for this resource
 if (markers != null) {
                     max = Math.max(max, basicFindMaxSeverity(markers, type, includeSubtypes));
                 }
                 return max < IMarker.SEVERITY_ERROR;
             }
         }
         MaxSeverityVisitor visitor = new MaxSeverityVisitor();
         new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
         return visitor.max;
     }

     /**
      * Adds the markers for a subtree of resources to the list.
      */
     private void visitorRemoveMarkers(IPath path, final String type, final boolean includeSubtypes) {
         IElementContentVisitor visitor = new IElementContentVisitor() {
             public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
                 ResourceInfo info = (ResourceInfo) elementContents;
                 if (info == null)
                     return false;
                 basicRemoveMarkers(info, requestor, type, includeSubtypes);
                 return true;
             }
         };
         new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor);
     }
 }

